package log

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"sync"
)

// Level type
type Level uint32

const (
	FatalLevel Level = iota
	ErrorLevel
	WarnLevel
	InfoLevel
	DebugLevel
)

var LevelName = map[Level]string{
	FatalLevel: "FATAL",
	ErrorLevel: "ERROR",
	WarnLevel:  "WARNING",
	InfoLevel:  "INFO",
	DebugLevel: "DEBUG",
}

type LogConfig struct {
	m          *log.Logger
	w          func(ctx context.Context) (io.Writer, error)
	l          Level
	dateLayout string
	formatter  func(ctx context.Context, record *Record) string
	processors []func(ctx context.Context, record *Record)
	mu         sync.Mutex
	ctx        context.Context
}

func (logConfig *LogConfig) SetLevel(l Level) {
	logConfig.mu.Lock()
	defer logConfig.mu.Unlock()
	logConfig.l = l
}

func (logConfig *LogConfig) SetFormatter(formatter func(ctx context.Context, record *Record) string) {
	logConfig.mu.Lock()
	defer logConfig.mu.Unlock()
	logConfig.formatter = formatter
}

func (logConfig *LogConfig) PushProcessor(processor func(ctx context.Context, record *Record)) {
	logConfig.mu.Lock()
	defer logConfig.mu.Unlock()
	logConfig.processors = append(logConfig.processors, processor)
}

func (logConfig *LogConfig) SetDateLayout(dateLayout string) {
	logConfig.mu.Lock()
	defer logConfig.mu.Unlock()
	logConfig.dateLayout = dateLayout
}

func (logConfig *LogConfig) SetWriter(w func(ctx context.Context) (io.Writer, error)) {
	logConfig.mu.Lock()
	defer logConfig.mu.Unlock()
	logConfig.w = w
}

func NewLogConfig(w io.Writer) *LogConfig {
	return &LogConfig{
		m:         log.New(w, "", 0),
		formatter: defaultFormatter,
	}
}

func defaultFormatter(_ context.Context, record *Record) string {
	var context, extra []byte
	var level string

	if record.Args != nil {
		context, _ = json.Marshal(record.Args)
	}

	if record.Extra != nil {
		extra, _ = json.Marshal(record.Extra)
	}

	if record.Prefix != "" {
		level = record.Prefix + "." + record.Level
	} else {
		level = record.Level
	}

	return fmt.Sprintf("%s %s %s %s %s\n", record.Date, level, record.Msg, context, extra)
}
